package base.originBase;

import base.enums.EnumRequestMethod;
import base.core.TestBase;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import utils.ConfigureProperty;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Supplier;


/**
 * case的模板方法，使用doIt即可完成一次简单的调用
 * 需要定制的地方可以通过重载的方式进行配置自定义
 * abstract方法的目的就是通过实现他来添加@Test注解
 * 同时可以固定外部调用doIt的时候固定参数位置
 *
 * @construct 无参构造将会使用默认的configureProperty
 *            来配置用户和不使用统一的baseHost地址
 *            可以通过配置来指定【账户】【host]数据
 * @field token 这个是账户token，不指定则会使用configure中的默认账号
 */

public abstract class BaseTemplate extends TestBase {
    public Logger logger = LogManager.getLogger();

    public void testUseDemo(){
        List<String> demoList = new ArrayList<>();
        demoList.add("以下将展示整个模板的调用顺序");
        demoList.add("1.首先，继承类必须实现方法：testCase，并在此方法上面添加@Test(dataProvider = \"data\"), 加上这个注解我就可以给他提供数据了");
        demoList.add("2.继承类直接调用doIt就行");
        demoList.add("3.doIt内部执行流程");
        demoList.add("4.doSomethingBeforeCall  调用接口前准备");
        demoList.add("5.call  调用接口");
        demoList.add("6.doSomethingAfterCall   调用接口后操作");
        demoList.add("7.judgeSuccess   与期望进行判断( 可自定义判断)");
        demoList.add("8.analyzers   自定义方法操作（函数指针）");

    }

    @Setter
    private String token;
    // 配置文件属性，在这里配置了host/account，用它可以获取指定账号的token
    @Setter
    @Getter
    private ConfigureProperty configureProperty;
    //默认不配置统一的host地址和host参数键
    private boolean useUnitHost = false;

    //保存每次返回的结果
    @Getter
    final private List<String> stepResults = new ArrayList<>();

    public BaseTemplate(){
        configureProperty = new ConfigureProperty();
    }
    public BaseTemplate(String account) {
        configureProperty = new ConfigureProperty(account, "");
    }

    /**
     * 控制使用自定义配置参数
     *
     * @param account 指定配置账号
     * @param useUnitHost 使用统一host开关 false为不使用，true为使用
     * @param baseHostKey 具体使用配置文件中的哪一个host，空则使用默认的
     */
    public BaseTemplate(String account, boolean useUnitHost, String baseHostKey){
        configureProperty = new ConfigureProperty(account, baseHostKey);
        this.useUnitHost = useUnitHost;
    }

    protected void initToken(String token) {
//        this.token = configureProperty.getToken();
    }

    //打印用例序号
    protected void showStep(String comment) {
        logger.info("测试用例：" + comment + " 开始执行");
    }

    //展示入参
    protected void showParameter(JSONObject formBody) {
        logger.info("form入参：" + formBody);
    }

    protected boolean judgeSuccessDefault(String result, JSONObject expected) {
        if (StrUtil.isEmpty(result))
            return false;
        JSONObject response = JSONObject.parseObject(result);
        String code = "code";
        return expected.getInteger(code).equals(response.getInteger(code));
    }

    public void doIt(List<BaseParameter> baseParameterList, String comment) {
        doIt(baseParameterList, comment, null, null, null);
    }

    public void doIt(List<BaseParameter> baseParameterList,
                     String comment, List<BiFunction<String, BaseParameter, Boolean>> analyzers) {
        doIt(baseParameterList, comment, null, analyzers, null);
    }

    /**
     * 执行测试用例
     *
     * @param baseParameterList 一个list代表一个完整吃测试用例
     * @param comment           本用例的描述
     * @param analyzers         多步骤用例的每一步的后处理
     * @param register          如果需要定时查看，则通过这个接口去注册
     */
    public void doIt(List<BaseParameter> baseParameterList, String comment,
                     List<BiFunction<String, JSONObject,  Boolean>> judgeSuccess,
                     List<BiFunction<String, BaseParameter, Boolean>> analyzers, Supplier register) {
        if (StrUtil.isEmpty(token))
            initToken("");
        stepResults.clear();

        for (int i = 0; i < baseParameterList.size(); ++i) {
            BaseParameter baseParameter = baseParameterList.get(i);
            logger.info("执行用例: " + comment + " 第" + (i + 1) + "/" + baseParameterList.size() + "步开始");
            showStep(baseParameter.getStep());
            showParameter(baseParameter.getRequest());
            String method = baseParameter.getMethod();

            doSomethingBeforeCall(baseParameter.getRequest().getInnerMap());

            String responseBody = null;
            try {
                responseBody = call(EnumRequestMethod.getMethod(method), baseParameter.getUrl(), token, baseParameter.getRequest().getInnerMap(),  baseParameter.getHeaders());
            } catch (Exception e) {
                e.printStackTrace();
            }
            logger.info("调用完成，返回值为: " + responseBody);
            responseBody = doSomethingAfterCall(responseBody);
            stepResults.add(responseBody);

            //判断是否返回的code为和期望一致
            boolean willEnd = true;
            if (judgeSuccess != null){
                willEnd = judgeSuccess.get(i).apply(responseBody, baseParameter.getResponse());
            }
            else{
                willEnd = judgeSuccessDefault(responseBody, baseParameter.getResponse());
            }

            if (willEnd)
                logger.info("执行用例: " + comment + " 第" + (i + 1) + "/" + baseParameterList.size() + "步成功");
            else {
                logger.error("执行用例: " + comment + " 第" + (i + 1) + "/" + baseParameterList.size() + "步失败!执行结束！");
                break;
            }

            //将前一步的返回值交给和下一步进行修改
            if (analyzers != null) {
                try {
                    logger.info("开始第" + i + "步交互处理");
                    try {
                        analyzers.get(i).apply(responseBody, baseParameterList.get(i + 1));
                    } catch (IndexOutOfBoundsException e) {
                        analyzers.get(i).apply(responseBody, null);
                    }

                } catch (Exception e) {
                    logger.error("有一个异常发生: " + e.toString() + " 它是" + comment + "的第【" + i + "】步的交互处理");
                }
            }
        }

        if (register != null && (boolean) register.get()) {
            logger.info("已将定时查看信息提交");
        }

        logger.info("用例" + comment + "执行完成\n");
    }


    // 在调用前对参数的调整如加入验签
    protected void doSomethingBeforeCall(Map<String, Object> map){}
    // 如在调用后需要解密啥的呢
    protected String doSomethingAfterCall(String response){return response;}

    /**
     * 一个默认的填空函数，如step1\2\3之间，2\3无交互就用这个填充
     * @param responseBody
     * @param baseParameter
     * @return
     */
    protected static boolean analyzerNullStep(String responseBody, BaseParameter baseParameter) {
        return true;
    }

    protected String call(EnumRequestMethod method, String url, String accessToken, Map<String, Object> map, Map<String, String> headers, Object... objs) throws Exception {
        if (useUnitHost)
            url = configureProperty.getHost() + url;
        logger.info(method + ":" + url + " \n  " + token.substring(0, 40));
        String responseBody = "";
        switch (method) {
            case GET:
                responseBody = get(url, accessToken, map, headers);
                break;
            case POST:
                responseBody = post(url, accessToken, map, headers);
                break;
            default:
                logger.info("不是POST/GET方式");
                return responseBody;
        }
        return responseBody;
    }

    protected String post(String url, String accessToken, Map<String, Object> map, Map<String, String> headers) {

        return HttpRequest.post(url).addHeaders(headers)
                .cookie(accessToken).form(map).execute().body();
//        String raw = (String)map.get("__raw__");
//        return HttpRequest.post(url).addHeaders(headers)
//                .cookie(accessToken).form(map).body(raw).execute().body();
    }

    protected String get(String url, String accessToken, Map<String, Object> map, Map<String, String> headers) {
        String raw = (String)map.get("__raw__");
        return HttpRequest.get(url).addHeaders(headers).cookie(accessToken).form(map).body(raw).execute().body();
    }

    /**
     *
     * 目的就是为了在实现类中写这个东西@Test(dataProvider = "data")统一调用
     * @param comment
     * @param parameterList
     */
    public abstract void testCase(String comment, List<BaseParameter> parameterList);
}
